package ptp;

import java.io.PrintStream;

/**
 * PTP Command, Data, Response, and Event blocks use a "Generic Container Structure" as a packet header.
 * 
 * <p>
 * Note that since the string values to which various codes map have been interned, you may safely rely on "==" and "!=" when you make
 * comparisons against constant values.
 * 
 */
abstract public class Container extends Buffer
{
	protected NameFactory factory;

	// get/put object handles (not 0, ~0) using session context
	// Session session;

	// fixed header layout, per annex D
	// NOTE: session id (9.3.2) is implicit: no multisession over USB
	// @ 0, u32 length
	// @ 4, u16 buffer type
	// @ 6, u16 code
	// @ 8, u32 xid
	// TOTAL: 12 bytes
	static final int HDR_LEN = 0;

	public Container(byte buf[], NameFactory f)
	{
		super(buf, buf.length);
		factory = f;
	}

	public Container(byte buf[], int len, NameFactory f)
	{
		super(buf, len);
		factory = f;
	}

	// package private
	protected void putHeader(int len, int type, int code, int xid)
	{
		if (offset != 0)
			throw new IllegalStateException();
		put32(len);
		put16(type);
		put16(code);
		put32(xid);
	}

	/**
	 * Provides a printable representation of data from the block header, including block type, code, and transaction ID.
	 */
	public String toString()
	{
		StringBuffer temp = new StringBuffer();
		String type = getBlockTypeName(getBlockType());
		int code = getCode();

		temp.append("{ ");
		temp.append(type);
		temp.append("; len ");
		temp.append(Integer.toString(getLength()));
		temp.append("; ");
		temp.append(getCodeName(code));
		temp.append("; xid ");
		temp.append(Integer.toString(getXID()));

		// inelegant, but ...
		if (this instanceof ParamVector)
		{
			ParamVector vec = (ParamVector) this;
			int nparams = vec.getNumParams();

			if (nparams > 0)
			{
				temp.append("; ");
				for (int i = 0; i < nparams; i++)
				{
					if (i != 0)
						temp.append(" ");
					temp.append("0x");
					temp.append(Integer.toHexString(vec.getParam(i)));
				}
			}
		}

		temp.append(" }");
		return temp.toString();
	}

	void dump(PrintStream out)
	{
		// out.println (toString ());
	}

	void parse()
	{
		offset = HDR_LEN;
	}

	/** Returns the overall length of this data block, including header. */
	public int getLength()
	{
		return /* unsigned */getS32(0);
	}

	/**
	 * Returns the overall type of this data block as a coded integer.
	 */
	public final int getBlockType()
	{
		return getU16(4);
	}

	/**
	 * Returns an interned string, normally "command", "data", "response", or "event", corresponding to the coded type. Unrecognized or
	 * undefined values are returned as interned Integer.toHexString values.
	 */
	public static final String getBlockTypeName(int type)
	{
		switch (type)
		{
		case 1:
			return "command";
		case 2:
			return "data";
		case 3:
			return "response";
		case 4:
			return "event";
		default:
			return Integer.toHexString(type).intern();
		}
	}

	/**
	 * Returns the operation, response, or event code of this block.
	 */
	public final int getCode()
	{
		return getU16(6);
	}

	/**
	 * Returns an interned string identifying the type of code field, such as "OperationCode", "ResponseCode", "ObjectFormatCode",
	 * "EventCode", or "DevicePropsCode". Unrecognized or undefined values are returned as interned Integer.toHexString values.
	 */
	public static final String getCodeType(int code)
	{
		switch (code >> 12)
		{
		case 1:
			return "OperationCode";
		case 2:
			return "ResponseCode";
		case 3:
			return "ObjectFormatCode";
		case 4:
			return "EventCode";
		case 5:
			return "DevicePropCode";
		case 8 + 1:
			return "Vendor-OpCode";
		case 8 + 2:
			return "Vendor-ResponseCode";
		case 8 + 3:
			return "Vendor-FormatCode";
		case 8 + 4:
			return "Vendor-EventCode";
		case 8 + 5:
			return "Vendor-PropCode";
		default:
			return Integer.toHexString(code >> 12).intern();
		}
	}

	/**
	 * Subclasses override this to map PTP codes to their names; the results are always interned strings, so that they can be efficiently
	 * compared ("=", "!=") against constants. Such per-instance methods permit type-specific subclasses (and vendor extensions) to name
	 * their code values, invoking superclass methods to name all other codes.
	 */
	public String getCodeName(int code)
	{
		return getCodeString(code);
	}

	/**
	 * Returns an interned string with name of this container's code.
	 */
	public final String getCodeString()
	{
		return getCodeName(getCode()).intern();
	}

	/**
	 * Returns an interned string with the hexadecimal value of the specified container code.
	 */
	public static String getCodeString(int code)
	{
		return Integer.toHexString(code).intern();
	}

	/**
	 * Returns the ID of the transaction this block is associated with.
	 */
	public final int getXID()
	{
		return getS32(8);
	}
}